/*
 * Engine.cpp
 *
 *  Created on: Oct 19, 2012
 *      Author: johnny
 */

#include "Engine.h"
#include "measurement/MeasDataSource.hpp"

#include <boost/format.hpp>

#ifndef WIN32
#include <dlfcn.h>
#else
#include <windows.h>
#endif

namespace core {

Engine& Engine::getInstance() {
	static Engine INSTANCE;
	return INSTANCE;
}

Engine::Engine(): inputHandler( GenericMI::getInstance()), cfgManager(ConfigurationManager::getInstance()),
                                running(false), currentIndex(0) {

}

Engine::~Engine() {
}

unsigned char Engine::getIndex() {
    unsigned char index;

    if(!indexes.empty()) {
        index = indexes.top();
        indexes.pop();
    } else
        index = currentIndex++;

    return index;
}

void Engine::dumpIndex(unsigned char index) {
    indexes.push(index);
}

measurement::MeasBase* Engine::getInstanceOfMO(boost::shared_ptr<measurement::MeasurementObject> mo) {
    typedef measurement::MeasBase* InstanceType;
    Logger& log = Logger::getInstance();

    // open library file
#ifdef WIN32
    HINSTANCE libHandle = LoadLibraryA(mo->path.c_str());
#else
    void* libHandle = dlopen(mo->path.c_str(), RTLD_LAZY);
#endif
    if(!libHandle) {
        log.add(Error, (boost::format("Engine::getInstanceOfMO (failed to open %1%)") % mo->path).str());
        return NULL;
    }

#ifdef WIN32
    typedef InstanceType (*LPGETMOINSTANCE)();
    LPGETMOINSTANCE fncInstanceHandle = (LPGETMOINSTANCE)GetProcAddress(libHandle, "getMOInstance");
#else
    void* fncInstanceHandle = dlsym(libHandle, "getMOInstance");
#endif
    if(!fncInstanceHandle) {
        log.add(Error, (boost::format(
            "Engine::getInstanceOfMO (MO %1% does not declare getMOInstance") % mo->path).str());
#ifdef WIN32
        FreeLibrary(libHandle);
#else
        dlclose(libHandle);
#endif
        return NULL;
    }

    InstanceType (*getType)() = (InstanceType(*)())(fncInstanceHandle);
    InstanceType meas = (*getType)();

    return meas;
}

void Engine::loadMOByIndex(unsigned char index) {
    boost::shared_ptr<measurement::MeasurementObject> mo;
    measurement::MeasBase* instance;

    for(MOStorage::const_iterator it = measurementObjects.begin();
        it != measurementObjects.end(); ++it) {
        if((*it)->index == index) {
            mo = *it;
            break;
        }
    }

    if(!mo) {
        Logger::getInstance().add(Error, "Failed to find MO for loading");
        return;
    }

    instance = getInstanceOfMO(mo);
    if(!instance) {
        Logger::getInstance().add(Error,
               (boost::format("Failed to load MO: %1%") % mo->name.c_str()).str());
        return;
    }

    boost::shared_ptr<measurement::MeasBase> measBase(instance);
    // connect mo to it's instance
    measBase->setMOLink(mo);
    measBase->setLoggerIfc(&Logger::getInstance());
    if(measBase->getMOLink()->type == measurement::DataSource ||
       measBase->getMOLink()->type == measurement::DataProcessor) {

        measurement::MeasDistributor *distribIfc = dynamic_cast<measurement::MeasDistributor*>(measBase.get());
        distribIfc->setDistributorIfc(this);
    }

    // setup connections
    // register as part of the current configuration
    cfgManager.addMO(measBase);

    // update UI
}

void Engine::removeMOByMeasIndex(unsigned short index) {
    const MeasBaseStorage &loaded = core::ConfigurationManager::getInstance().getLoadedMOs();

    for(MeasBaseStorage::const_iterator it = loaded.begin();
        it != loaded.end(); ++it) {

        if((*it)->getMeasIndex() == index) {
            core::ConfigurationManager::getInstance().removeMO(*it);

            return;
        }
    }
}

bool Engine::findFiles(const boost::filesystem::path& dir,
			const std::wstring& fileExt,
			std::list<boost::filesystem::path>& libs) {

	using namespace boost::filesystem;

	directory_iterator endIterator;
	for(directory_iterator it(dir); it != endIterator; ++it) {
		if(is_directory(it->status()))
			return findFiles(it->path(), fileExt, libs);
		else {
			if(is_regular_file(it->path()) && it->path().extension() == fileExt)
				libs.push_back(*it);
		}
	}

	return true;
}

boost::shared_ptr<measurement::MeasurementObject> Engine::isMO(const boost::filesystem::path &p) {
    typedef measurement::MeasObjType GetTypeFunc;
    typedef wchar_t* GetDetailsWFunc;
    typedef measurement::MeasObjSDetail GetDetailsSFunc;

    Logger& log = Logger::getInstance();
    boost::shared_ptr<measurement::MeasurementObject> obj;

    // open library file
#ifdef WIN32
    HINSTANCE libHandle = LoadLibraryA(p.string().c_str());
#else
    void* libHandle = dlopen(p.c_str(), RTLD_LAZY);
#endif
    if(!libHandle) {
        log.add(Debug, (boost::wformat(L"Engine::isMO (failed to open %1%)") % p.c_str()).str());
        return obj;
    }

#ifdef WIN32
    typedef measurement::MeasObjType		(*LPGETOBJTYPE)();
    typedef wchar_t*                		(*LPGETOBJWDETAIL)();

    LPGETOBJTYPE	fncTypeHandle;
    LPGETOBJWDETAIL fncNameHandle;
    LPGETOBJWDETAIL	fncAuthorHandle;
    LPGETOBJWDETAIL	fncInputTypeHandle;
    LPGETOBJWDETAIL	fncOutputTypeHandle;
#else
	void* fncTypeHandle;
	void* fncNameHandle;
	void* fncAuthorHandle;
	void* fncInputTypeHandle;
	void* fncOutputTypeHandle;
#endif

    // check for needed functions
#ifdef WIN32
    fncTypeHandle		= (LPGETOBJTYPE)GetProcAddress(libHandle, "getMOType");
    fncNameHandle		= (LPGETOBJWDETAIL)GetProcAddress(libHandle, "getMOName");
    fncAuthorHandle		= (LPGETOBJWDETAIL)GetProcAddress(libHandle, "getMOAuthor");
    fncInputTypeHandle	= (LPGETOBJWDETAIL)GetProcAddress(libHandle, "getMOInputType");
    fncOutputTypeHandle	= (LPGETOBJWDETAIL)GetProcAddress(libHandle, "getMOOutputType");
#else
    fncTypeHandle       = dlsym(libHandle, "getMOType");
    fncNameHandle       = dlsym(libHandle, "getMOName");
    fncAuthorHandle     = dlsym(libHandle, "getMOAuthor");
    fncInputTypeHandle  = dlsym(libHandle, "getMOInputType");
    fncOutputTypeHandle = dlsym(libHandle, "getMOOutputType");
#endif
    if(!fncTypeHandle || !fncNameHandle || !fncAuthorHandle) {
        log.add(Debug, (boost::wformat(L"Engine::isMO (not an MO %1%") % p.c_str()).str(), ENGINE_ENTITY_NAME);

#ifdef WIN32
        FreeLibrary(libHandle);
#else
        dlclose(libHandle);
#endif
        return obj;
    }

    measurement::MeasObjType        type;
    measurement::MeasObjWDetail     name;
    measurement::MeasObjWDetail     author;
    measurement::MeasObjWDetail     inputType, outputType;
    measurement::MeasObjSDetail     category;

#ifndef WIN32
    GetTypeFunc      (*getType)()    = (GetTypeFunc(*)())(fncTypeHandle);
    GetDetailsWFunc  (*getName)()    = (GetDetailsWFunc(*)())(fncNameHandle);
    GetDetailsWFunc  (*getAuthor)()  = (GetDetailsWFunc(*)())(fncAuthorHandle);

    type = (*getType)();
	name = (*getName)();
	author = (*getAuthor)();
#else
    type = fncTypeHandle();
    name = fncNameHandle();
    author = fncAuthorHandle();
#endif

    switch(type) {
    case measurement::DataSource:
        category = MO_CATEGORY_DATA_SOURCE;
        break;
    case measurement::DataProcessor:
        category = MO_CATEGORY_DATA_PROCESSOR;
        break;
    case measurement::Visualization:
        category = MO_CATEGORY_VISUALIZATION;
        break;
    }

    if(fncInputTypeHandle) {
#ifndef WIN32
        GetDetailsWFunc (*getInputType)() = (GetDetailsWFunc(*)())(fncInputTypeHandle);
        inputType = (*getInputType)();
#else
        inputType = fncInputTypeHandle();
#endif
    }

    if(fncOutputTypeHandle) {
#ifndef WIN32
        GetDetailsWFunc (*getOutputType)() = (GetDetailsWFunc(*)())(fncOutputTypeHandle);
        outputType = (*getOutputType)();
#else
        outputType = fncOutputTypeHandle();
#endif
    }

    obj = boost::shared_ptr<measurement::MeasurementObject>(
                new measurement::MeasurementObject(name, author, type, inputType, outputType,
                                                   p.string(), category, getIndex()));

    return obj;
}

bool Engine::scanMOs(const boost::filesystem::path& directory) {
	namespace fs = boost::filesystem;
    typedef std::list<boost::filesystem::path> LibStorage;

    boost::shared_ptr<measurement::MeasurementObject> measObj;
	std::wstring moExt;
    LibStorage libraries;

#ifdef WIN32
	moExt = L".dll";
#else
	moExt = L".so";
#endif

	if(fs::exists(directory) && fs::is_directory(directory)) {
		findFiles(directory, moExt, libraries);
	} else
		return false;

	if(!libraries.empty()) {
        for(LibStorage::const_iterator it = libraries.begin();
            it != libraries.end(); ++it) {

            measObj = isMO(*it);
            if(measObj) {
                measurementObjects.push_back(measObj);
                Logger::getInstance().add(Debug, (boost::wformat(L"Scanned MO: %1%") % measObj->name).str(),
                                          ENGINE_ENTITY_NAME);
            } else {
                Logger::getInstance().add(Debug, (boost::wformat(L"Not a valid MO: %1%") % *it).str(),
                                          ENGINE_ENTITY_NAME);
            }
        }
	}

    Logger::getInstance().add(Debug, L"Engine: Finished MO scan", ENGINE_ENTITY_NAME);
    return true;
}

void Engine::addScannedMO(measurement::MeasurementObject *mo) {
    measurementObjects.push_back(
                boost::shared_ptr<measurement::MeasurementObject>(mo));
}

void Engine::loadSysMOs() {
    //sys::initMOs();
}

void Engine::distribute(const unsigned short measIndex, DataPackage *package) {
    ConnectionsList& connections = ConfigurationManager::getInstance().getMeasConnHandler().getConnections();

    SharedMOPtr from, to;
    for(ConnectionsList::iterator it = connections.begin();
        it != connections.end(); ++it) {

        from = (*it)->first.lock();
        to = (*it)->second.lock();

        if(from->getMeasIndex() == measIndex) {
            reinterpret_cast<measurement::MeasReceiver*>(to.get())->validateData(*package);
        }
    }
}

void Engine::run() {
    running = true;

    //const std::list<SharedMOPtr>& loaded = ConfigurationManager::getInstance().getLoadedMOs();

    while(running) {

    }
}

void Engine::stopRunning() {
    running = false;
}

unsigned short Engine::generateHandle() {
    return moHandle++;
}

const MOStorage& Engine::getMOList() {
    return measurementObjects;
}

} /* namespace core */
